﻿using System.Linq;
using BBCode.Collections;
using BBCode.Nodes;
using BBCode.Tags;

namespace BBCode
{
    /// <summary>
    /// The parser, this should be setup with the desired tags and smileys and then used to parse the text.
    /// </summary>
    public class Parser
    {
        private readonly TagCollection _smileys;
        private readonly TagCollection _tags;

        /// <summary>
        /// Initializes a new instance of the <see cref="Parser"/> class.
        /// </summary>
        /// <param name="smileys">The smileys to be used.</param>
        /// <param name="tags">The tags to be used.</param>
        public Parser(TagCollection smileys, TagCollection tags)
        {
            _smileys = smileys;
            //Order tags so we can't possibly add children to "final" nodes
            var tempTags = from t in tags orderby t.CanHaveChildren descending select t;
            _tags = new TagCollection(tempTags);
        }

        /// <summary>
        /// Gets the tags used in this parser.
        /// </summary>
        public TagCollection Tags
        {
            get { return _tags; }
        }

        /// <summary>
        /// Gets the smileys used in this parser.
        /// </summary>
        public TagCollection Smileys
        {
            get { return _smileys; }
        }


        /// <summary>
        /// Parses the nodes.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        private NodeCollection ParseNodes(string text)
        {
            var nodes = new NodeCollection();

            //Get all nodes
            foreach (var tag in Tags)
            {
                nodes.AddRange(tag.GetNodes(text));
            }

            //Get all smileys
            foreach (var tag in Smileys)
            {
                nodes.AddRange(tag.GetNodes(text));
            }

            return nodes;
        }

        /// <summary>
        /// Sorts the nodes.
        /// </summary>
        /// <param name="rawResults">The raw results.</param>
        /// <returns></returns>
        private NodeCollection SortNodes(NodeCollection rawResults)
        {
            var foundNodes = new NodeCollection();

            while (rawResults.Count > 0)
            {
                var node = rawResults.OrderBy(a => a.NodeIndex.End).First();

                //Check if there is a parent or not
                var possibleParent = (from n in rawResults
                                        where
                                            n.NodeIndex.Beginning < node.NodeIndex.Beginning &&
                                            n.NodeIndex.End > node.NodeIndex.End
                                        orderby n.NodeIndex.Beginning descending
                                        select n).FirstOrDefault();

                if (possibleParent == null)
                {
                    //No parent found, add to the foundNodes
                    foundNodes.Add(node);
                }
                else
                {
                    //Hey, we found the parent, let's add that shit yo!
                    ((INestableNode) possibleParent).Children.Add(node);
                    node.Parent = possibleParent;
                }

                rawResults.Remove(node);
            }

            return foundNodes;
        }

        /// <summary>
        /// Abortes the specified nodes.
        /// </summary>
        /// <param name="nodes">The nodes.</param>
        private void Abortion(NodeCollection nodes)
        {
            //Get rid of the UnwantedChildren (lol abortus joke inc!)
            foreach (var nestableNode in nodes.OfType<INestableNode>().Where(nestableNode => nestableNode.HasChildren))
            {
                if (nestableNode.Tag.CanHaveChildren)
                {
                    Abortion(nestableNode.Children);
                }
                else
                {
                    //Abortus time!!!
                    nestableNode.Children.Clear();
                }
            }
        }

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        public ParseResult Parse(string text)
        {
            //Get all the resulting nodes, it's possible "children" are found before the parents
            var results = ParseNodes(text);
            //Let's get the children attached to the right parents!
            results = SortNodes(results);
            Abortion(results);

            //Let's return our results!
            return new ParseResult {Nodes = results, Text = text};
        }
    }
}